/*
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * This software is open source.
 * See the bottom of this file for the licence.
 */

package org.dom4j.io;

import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Stack;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Entity;
import org.dom4j.Node;

import org.xml.sax.SAXException;

/**
 * <p> <code>HTMLWriter</code> takes a DOM4J tree and formats it to a stream
 * as HTML. This formatter is similar to XMLWriter but it outputs the text of
 * CDATA and Entity sections rather than the serialised format as in XML, it has
 * an XHTML mode, it retains whitespace in certain elements such as &lt;PRE&gt;,
 * and it supports certain elements which have no corresponding close tag such
 * as for &lt;BR&gt; and &lt;P&gt;. </p>
 * 
 * <p> The OutputFormat passed in to the constructor is checked for isXHTML()
 * and isExpandEmptyElements(). See {@link OutputFormat OutputFormat}for
 * details. Here are the rules for <b>this class </b> based on an OutputFormat,
 * "format", passed in to the constructor: <br/><br/>
 * 
 * <ul> <li>If an element is in {@link #getOmitElementCloseSet()
 * getOmitElementCloseSet}, then it is treated specially:
 * 
 * <ul> <li>It never expands, since some browsers treat this as two separate
 * Horizontal Rules: &lt;HR&gt;&lt;/HR&gt;</li> <li>If
 * {@link org.dom4j.io.OutputFormat#isXHTML() format.isXHTML()}, then it has a
 * space before the closing single-tag slash, since Netscape 4.x- treats this:
 * &lt;HR /&gt; as an element named "HR" with an attribute named "/", but that's
 * better than when it refuses to recognize this: &lt;hr/&gt; which it thinks is
 * an element named "HR/".</li> </ul>
 * 
 * </li> <li>If {@link org.dom4j.io.OutputFormat#isXHTML() format.isXHTML()},
 * all elements must have either a close element, or be a closed single tag.</li>
 * <li>If {@link org.dom4j.io.OutputFormat#isExpandEmptyElements()
 * format.isExpandEmptyElements()}() is true, all elements are expanded except
 * as above.</li> </ul>
 * 
 * <b>Examples </b> </p>
 * 
 * <p> </p>
 * 
 * <p> If isXHTML == true, CDATA sections look like this:
 * 
 * <PRE>
 * 
 * <b>&lt;myelement&gt;&lt;![CDATA[My data]]&gt;&lt;/myelement&gt; </b>
 * 
 * </PRE>
 * 
 * Otherwise, they look like this:
 * 
 * <PRE>
 * 
 * <b>&lt;myelement&gt;My data&lt;/myelement&gt; </b>
 * 
 * </PRE>
 * 
 * </p>
 * 
 * <p> Basically, {@link OutputFormat.isXHTML() OutputFormat.isXHTML()} ==
 * <code>true</code> will produce valid XML, while {@link
 * org.dom4j.io.OutputFormat#isExpandEmptyElements()
 * format.isExpandEmptyElements()} determines whether empty elements are
 * expanded if isXHTML is true, excepting the special HTML single tags. </p>
 * 
 * <p> Also, HTMLWriter handles tags whose contents should be preformatted, that
 * is, whitespace-preserved. By default, this set includes the tags &lt;PRE&gt;,
 * &lt;SCRIPT&gt;, &lt;STYLE&gt;, and &lt;TEXTAREA&gt;, case insensitively. It
 * does not include &lt;IFRAME&gt;. Other tags, such as &lt;CODE&gt;,
 * &lt;KBD&gt;, &lt;TT&gt;, &lt;VAR&gt;, are usually rendered in a different
 * font in most browsers, but don't preserve whitespace, so they also don't
 * appear in the default list. HTML Comments are always whitespace-preserved.
 * However, the parser you use may store comments with linefeed-only text nodes
 * (\n) even if your platform uses another line.separator character, and
 * HTMLWriter outputs Comment nodes exactly as the DOM is set up by the parser.
 * See examples and discussion here: {@link#setPreformattedTags(java.util.Set)
 * setPreformattedTags} </p>
 * 
 * <p> <b>Examples </b> </p> <blockquote> <p> <b>Pretty Printing </b> </p>
 * 
 * <p> This example shows how to pretty print a string containing a valid HTML
 * document to a string. You can also just call the static methods of this
 * class: <br> {@link #prettyPrintHTML(String) prettyPrintHTML(String)}or <br>
 * {@link #prettyPrintHTML(String,boolean,boolean,boolean,boolean)
 * prettyPrintHTML(String,boolean,boolean,boolean,boolean)} or, <br>
 * {@link #prettyPrintXHTML(String) prettyPrintXHTML(String)}for XHTML (note
 * the X) </p>
 * 
 * <pre>
 * String testPrettyPrint(String html) {
 *     StringWriter sw = new StringWriter();
 *     OutputFormat format = OutputFormat.createPrettyPrint();
 *     // These are the default values for createPrettyPrint,
 *     // so you needn't set them:
 *     // format.setNewlines(true);
 *     // format.setTrimText(true);&lt;/font&gt;
 *     format.setXHTML(true);
 *     HTMLWriter writer = new HTMLWriter(sw, format);
 *     Document document = DocumentHelper.parseText(html);
 *     writer.write(document);
 *     writer.flush();
 *     return sw.toString();
 * }
 * </pre>
 * 
 * <p> This example shows how to create a "squeezed" document, but one that will
 * work in browsers even if the browser line length is limited. No newlines are
 * included, no extra whitespace at all, except where it it required by
 * {@link #setPreformattedTags(java.util.Set) setPreformattedTags}. </p>
 * 
 * <pre>
 * String testCrunch(String html) {
 *     StringWriter sw = new StringWriter();
 *     OutputFormat format = OutputFormat.createPrettyPrint();
 *     format.setNewlines(false);
 *     format.setTrimText(true);
 *     format.setIndent(&quot;&quot;);
 *     format.setXHTML(true);
 *     format.setExpandEmptyElements(false);
 *     format.setNewLineAfterNTags(20);
 *     org.dom4j.io.HTMLWriter writer = new HTMLWriter(sw, format);
 *     org.dom4j.Document document = DocumentHelper.parseText(html);
 *     writer.write(document);
 *     writer.flush();
 *     return sw.toString();
 * }
 * </pre>
 * 
 * </blockquote>
 * 
 * @author <a href="mailto:james.strachan@metastuff.com">James Strachan </a>
 * @author Laramie Crocker
 * @version $Revision: 1.2 $
 */
public class HTMLWriter extends XMLWriter
{
   private static String lineSeparator = System.getProperty("line.separator");

   protected static final HashSet DEFAULT_PREFORMATTED_TAGS;

   static
   {
      // If you change this list, update the javadoc examples, above in the
      // class javadoc, in writeElement, and in setPreformattedTags().
      DEFAULT_PREFORMATTED_TAGS = new HashSet();
      DEFAULT_PREFORMATTED_TAGS.add("PRE");
      DEFAULT_PREFORMATTED_TAGS.add("SCRIPT");
      DEFAULT_PREFORMATTED_TAGS.add("STYLE");
      DEFAULT_PREFORMATTED_TAGS.add("TEXTAREA");
   }

   protected static final OutputFormat DEFAULT_HTML_FORMAT;

   static
   {
      DEFAULT_HTML_FORMAT = new OutputFormat("  ", true);
      DEFAULT_HTML_FORMAT.setTrimText(true);
      DEFAULT_HTML_FORMAT.setSuppressDeclaration(true);
   }

   private Stack formatStack = new Stack();

   private String lastText = "";

   private int tagsOuput = 0;

   // legal values are 0+, but -1 signifies lazy initialization.
   private int newLineAfterNTags = -1;

   private HashSet preformattedTags = DEFAULT_PREFORMATTED_TAGS;

   /**
    * Used to store the qualified element names which should have no close
    * element tag
    */
   private HashSet omitElementCloseSet;

   public HTMLWriter(Writer writer)
   {
      super(writer, DEFAULT_HTML_FORMAT);
   }

   public HTMLWriter(Writer writer, OutputFormat format)
   {
      super(writer, format);
   }

   public HTMLWriter() throws UnsupportedEncodingException
   {
      super(DEFAULT_HTML_FORMAT);
   }

   public HTMLWriter(OutputFormat format) throws UnsupportedEncodingException
   {
      super(format);
   }

   public HTMLWriter(OutputStream out) throws UnsupportedEncodingException
   {
      super(out, DEFAULT_HTML_FORMAT);
   }

   public HTMLWriter(OutputStream out, OutputFormat format)
      throws UnsupportedEncodingException
   {
      super(out, format);
   }

   public void startCDATA() throws SAXException
   {
   }

   public void endCDATA() throws SAXException
   {
   }

   // Overloaded methods
   // added isXHTML() stuff so you get the CDATA brackets if you desire.
   protected void writeCDATA(String text) throws IOException
   {
      // XXX: Should we escape entities?
      // writer.write( escapeElementEntities( text ) );
      if(getOutputFormat().isXHTML())
      {
         super.writeCDATA(text);
      }
      else
      {
         writer.write(text);
      }

      lastOutputNodeType = Node.CDATA_SECTION_NODE;
   }

   protected void writeEntity(Entity entity) throws IOException
   {
      writer.write(entity.getText());
      lastOutputNodeType = Node.ENTITY_REFERENCE_NODE;
   }

   protected void writeDeclaration() throws IOException
   {
   }

   protected void writeString(String text) throws IOException
   {
      /*
       * DOM stores \n at the end of text nodes that are newlines. This is
       * significant if we are in a PRE section. However, we only want to output
       * the system line.separator, not \n. This is a little brittle, but this
       * function appears to be called with these lineseparators as a separate
       * TEXT_NODE. If we are in a preformatted section, output the right
       * line.separator, otherwise ditch. If the single \n character is not the
       * text, then do the super thing to output the text.
       * 
       * Also, we store the last text that was not a \n since it may be used by
       * writeElement in this class to line up preformatted tags.
       */
      if(text.equals("\n"))
      {
         if(!formatStack.empty())
         {
            super.writeString(lineSeparator);
         }

         return;
      }

      lastText = text;

      if(formatStack.empty())
      {
         super.writeString(text.trim());
      }
      else
      {
         super.writeString(text);
      }
   }

   /**
    * Overriden method to not close certain element names to avoid wierd
    * behaviour from browsers for versions up to 5.x
    * 
    * @param qualifiedName DOCUMENT ME!
    * 
    * @throws IOException DOCUMENT ME!
    */
   protected void writeClose(String qualifiedName) throws IOException
   {
      if(!omitElementClose(qualifiedName))
      {
         super.writeClose(qualifiedName);
      }
   }

   protected void writeEmptyElementClose(String qualifiedName)
      throws IOException
   {
      if(getOutputFormat().isXHTML())
      {
         // xhtml, always check with format object whether to expand or not.
         if(omitElementClose(qualifiedName))
         {
            // it was a special omit tag, do it the XHTML way: "<br/>",
            // ignoring the expansion option, since <br></br> is OK XML,
            // but produces twice the linefeeds desired in the browser.
            // for netscape 4.7, though all are fine with it, write a space
            // before the close slash.
            writer.write(" />");
         }
         else
         {
            super.writeEmptyElementClose(qualifiedName);
         }
      }
      else
      {
         // html, not xhtml
         if(omitElementClose(qualifiedName))
         {
            // it was a special omit tag, do it the old html way: "<br>".
            writer.write(">");
         }
         else
         {
            // it was NOT a special omit tag, check with format object
            // whether to expand or not.
            super.writeEmptyElementClose(qualifiedName);
         }
      }
   }

   protected boolean omitElementClose(String qualifiedName)
   {
      return internalGetOmitElementCloseSet().contains(
         qualifiedName.toUpperCase());
   }

   private HashSet internalGetOmitElementCloseSet()
   {
      if(omitElementCloseSet == null)
      {
         omitElementCloseSet = new HashSet();
         loadOmitElementCloseSet(omitElementCloseSet);
      }

      return omitElementCloseSet;
   }

   // If you change this, change the javadoc for getOmitElementCloseSet.
   protected void loadOmitElementCloseSet(Set set)
   {
      set.add("AREA");
      set.add("BASE");
      set.add("BR");
      set.add("COL");
      set.add("HR");
      set.add("IMG");
      set.add("INPUT");
      set.add("LINK");
      set.add("META");
      set.add("P");
      set.add("PARAM");
   }

   // let the people see the set, but not modify it.

   /**
    * A clone of the Set of elements that can have their close-tags omitted. By
    * default it should be "AREA", "BASE", "BR", "COL", "HR", "IMG", "INPUT",
    * "LINK", "META", "P", "PARAM"
    * 
    * @return A clone of the Set.
    */
   public Set getOmitElementCloseSet()
   {
      return (Set) (internalGetOmitElementCloseSet().clone());
   }

   /**
    * To use the empty set, pass an empty Set, or null:
    * 
    * <pre>
    * 
    * 
    *       setOmitElementCloseSet(new HashSet());
    *     or
    *       setOmitElementCloseSet(null);
    * 
    *  
    * </pre>
    * 
    * @param newSet DOCUMENT ME!
    */
   public void setOmitElementCloseSet(Set newSet)
   {
      // resets, and safely empties it out if newSet is null.
      omitElementCloseSet = new HashSet();

      if(newSet != null)
      {
         omitElementCloseSet = new HashSet();

         Object aTag;
         Iterator iter = newSet.iterator();

         while(iter.hasNext())
         {
            aTag = iter.next();

            if(aTag != null)
            {
               omitElementCloseSet.add(aTag.toString().toUpperCase());
            }
         }
      }
   }

   /**
    * @see #setPreformattedTags(java.util.Set) setPreformattedTags
    */
   public Set getPreformattedTags()
   {
      return (Set) (preformattedTags.clone());
   }

   /**
    * <p> Override the default set, which includes PRE, SCRIPT, STYLE, and
    * TEXTAREA, case insensitively. </p>
    * 
    * <p> <b>Setting Preformatted Tags </b> </p>
    * 
    * <p> Pass in a Set of Strings, one for each tag name that should be treated
    * like a PRE tag. You may pass in null or an empty Set to assign the empty
    * set, in which case no tags will be treated as preformatted, except that
    * HTML Comments will continue to be preformatted. If a tag is included in
    * the set of preformatted tags, all whitespace within the tag will be
    * preserved, including whitespace on the same line preceding the close tag.
    * This will generally make the close tag not line up with the start tag, but
    * it preserves the intention of the whitespace within the tag. </p>
    * 
    * <p> The browser considers leading whitespace before the close tag to be
    * significant, but leading whitespace before the open tag to be
    * insignificant. For example, if the HTML author doesn't put the close
    * TEXTAREA tag flush to the left margin, then the TEXTAREA control in the
    * browser will have spaces on the last line inside the control. This may be
    * the HTML author's intent. Similarly, in a PRE, the browser treats a
    * flushed left close PRE tag as different from a close tag with leading
    * whitespace. Again, this must be left up to the HTML author. </p>
    * 
    * <p> <b>Examples </b> </p> <blockquote> <p> Here is an example of how you
    * can set the PreformattedTags list using setPreformattedTags to include
    * IFRAME, as well as the default set, if you have an instance of this class
    * named myHTMLWriter:
    * 
    * <pre>
    * Set current = myHTMLWriter.getPreformattedTags();
    * current.add(&quot;IFRAME&quot;);
    * myHTMLWriter.setPreformattedTags(current);
    * 
    * //The set is now &lt;b&gt;PRE, SCRIPT, STYLE, TEXTAREA, IFRAME&lt;/b&gt;
    * 
    * 
    * </pre>
    * 
    * Similarly, you can simply replace it with your own:
    * 
    * <pre>
    * 
    * 
    *       HashSet newset = new HashSet();
    *       newset.add(&quot;PRE&quot;);
    *       newset.add(&quot;TEXTAREA&quot;);
    *       myHTMLWriter.setPreformattedTags(newset);
    * 
    *       //The set is now &lt;b&gt;{PRE, TEXTAREA}&lt;/b&gt;
    * 
    *  
    * </pre>
    * 
    * You can remove all tags from the preformatted tags list, with an empty
    * set, like this:
    * 
    * <pre>
    * 
    * 
    *       myHTMLWriter.setPreformattedTags(new HashSet());
    * 
    *       //The set is now &lt;b&gt;{}&lt;/b&gt;
    * 
    *  
    * </pre>
    * 
    * or with null, like this:
    * 
    * <pre>
    * 
    * 
    *       myHTMLWriter.setPreformattedTags(null);
    * 
    *       //The set is now &lt;b&gt;{}&lt;/b&gt;
    * 
    *  
    * </pre>
    * 
    * </p> </blockquote>
    * 
    * @param newSet DOCUMENT ME!
    */
   public void setPreformattedTags(Set newSet)
   {
      // no fancy merging, just set it, assuming they did a
      // getExcludeTrimTags() first if they wanted to preserve the default
      // set.
      // resets, and safely empties it out if newSet is null.
      preformattedTags = new HashSet();

      if(newSet != null)
      {
         Object aTag;
         Iterator iter = newSet.iterator();

         while(iter.hasNext())
         {
            aTag = iter.next();

            if(aTag != null)
            {
               preformattedTags.add(aTag.toString().toUpperCase());
            }
         }
      }
   }

   /**
    * DOCUMENT ME!
    * 
    * @param qualifiedName DOCUMENT ME!
    * 
    * @return true if the qualifiedName passed in matched (case-insensitively) a
    *         tag in the preformattedTags set, or false if not found or if the
    *         set is empty or null.
    * 
    * @see #setPreformattedTags(java.util.Set) setPreformattedTags
    */
   public boolean isPreformattedTag(String qualifiedName)
   {
      // A null set implies that the user called setPreformattedTags(null),
      // which means they want no tags to be preformatted.
      return (preformattedTags != null)
         && (preformattedTags.contains(qualifiedName.toUpperCase()));
   }

   /**
    * This override handles any elements that should not remove whitespace, such
    * as &lt;PRE&gt;, &lt;SCRIPT&gt;, &lt;STYLE&gt;, and &lt;TEXTAREA&gt;. Note:
    * the close tags won't line up with the open tag, but we can't alter that.
    * See javadoc note at setPreformattedTags.
    * 
    * @param element DOCUMENT ME!
    * 
    * @throws IOException When the stream could not be written to.
    * 
    * @see #setPreformattedTags(java.util.Set) setPreformattedTags
    */
   protected void writeElement(Element element) throws IOException
   {
      if(newLineAfterNTags == -1)
      { // lazy initialization check
         lazyInitNewLinesAfterNTags();
      }

      if(newLineAfterNTags > 0)
      {
         if((tagsOuput > 0) && ((tagsOuput % newLineAfterNTags) == 0))
         {
            super.writer.write(lineSeparator);
         }
      }

      tagsOuput++;

      String qualifiedName = element.getQualifiedName();
      String saveLastText = lastText;
      int size = element.nodeCount();

      if(isPreformattedTag(qualifiedName))
      {
         OutputFormat currentFormat = getOutputFormat();
         boolean saveNewlines = currentFormat.isNewlines();
         boolean saveTrimText = currentFormat.isTrimText();
         String currentIndent = currentFormat.getIndent();

         // You could have nested PREs, or SCRIPTS within PRE... etc.,
         // therefore use push and pop.
         formatStack.push(new FormatState(saveNewlines, saveTrimText,
            currentIndent));

         try
         {
            // do this manually, since it won't be done while outputting
            // the tag.
            super.writePrintln();

            if((saveLastText.trim().length() == 0) && (currentIndent != null)
               && (currentIndent.length() > 0))
            {
               // We are indenting, but we want to line up with the close
               // tag. lastText was the indent (whitespace, no \n) before
               // the preformatted start tag. So write it out instead of
               // the current indent level. This makes it line up with its
               // close tag.
               super.writer.write(justSpaces(saveLastText));
            }

            // actually, newlines are handled in this class by writeString,
            // depending on if the stack is empty.
            currentFormat.setNewlines(false);
            currentFormat.setTrimText(false);
            currentFormat.setIndent("");

            // This line is the recursive one:
            super.writeElement(element);
         }
         finally
         {
            FormatState state = (FormatState) formatStack.pop();
            currentFormat.setNewlines(state.isNewlines());
            currentFormat.setTrimText(state.isTrimText());
            currentFormat.setIndent(state.getIndent());
         }
      }
      else
      {
         super.writeElement(element);
      }
   }

   private String justSpaces(String text)
   {
      int size = text.length();
      StringBuffer res = new StringBuffer(size);
      char c;

      for(int i = 0; i < size; i++)
      {
         c = text.charAt(i);

         switch(c)
         {
         case '\r':
         case '\n':

            continue;

         default:
            res.append(c);
         }
      }

      return res.toString();
   }

   private void lazyInitNewLinesAfterNTags()
   {
      if(getOutputFormat().isNewlines())
      {
         // don't bother, newlines are going to happen anyway.
         newLineAfterNTags = 0;
      }
      else
      {
         newLineAfterNTags = getOutputFormat().getNewLineAfterNTags();
      }
   }

   // Convenience methods, static, with bunch-o-defaults

   /**
    * Convenience method to just get a String result.
    * 
    * @param html DOCUMENT ME!
    * 
    * @return a pretty printed String from the source string, preserving
    *         whitespace in the defaultPreformattedTags set, and leaving the
    *         close tags off of the default omitElementCloseSet set. Use one of
    *         the write methods if you want stream output.
    * 
    * @throws java.io.IOException
    * @throws java.io.UnsupportedEncodingException
    * @throws org.dom4j.DocumentException
    */
   public static String prettyPrintHTML(String html)
      throws java.io.IOException, java.io.UnsupportedEncodingException,
      org.dom4j.DocumentException
   {
      return prettyPrintHTML(html, true, true, false, true);
   }

   /**
    * Convenience method to just get a String result, but <b>As XHTML </b>.
    * 
    * @param html DOCUMENT ME!
    * 
    * @return a pretty printed String from the source string, preserving
    *         whitespace in the defaultPreformattedTags set, but conforming to
    *         XHTML: no close tags are omitted (though if empty, they will be
    *         converted to XHTML empty tags: &lt;HR/&gt; Use one of the write
    *         methods if you want stream output.
    * 
    * @throws java.io.IOException
    * @throws java.io.UnsupportedEncodingException
    * @throws org.dom4j.DocumentException
    */
   public static String prettyPrintXHTML(String html)
      throws java.io.IOException, java.io.UnsupportedEncodingException,
      org.dom4j.DocumentException
   {
      return prettyPrintHTML(html, true, true, true, false);
   }

   /**
    * DOCUMENT ME!
    * 
    * @param html DOCUMENT ME!
    * @param newlines DOCUMENT ME!
    * @param trim DOCUMENT ME!
    * @param isXHTML DOCUMENT ME!
    * @param expandEmpty DOCUMENT ME!
    * 
    * @return a pretty printed String from the source string, preserving
    *         whitespace in the defaultPreformattedTags set, and leaving the
    *         close tags off of the default omitElementCloseSet set. This
    *         override allows you to specify various formatter options. Use one
    *         of the write methods if you want stream output.
    * 
    * @throws java.io.IOException
    * @throws java.io.UnsupportedEncodingException
    * @throws org.dom4j.DocumentException
    */
   public static String prettyPrintHTML(String html, boolean newlines,
      boolean trim, boolean isXHTML, boolean expandEmpty)
      throws java.io.IOException, java.io.UnsupportedEncodingException,
      org.dom4j.DocumentException
   {
      StringWriter sw = new StringWriter();
      OutputFormat format = OutputFormat.createPrettyPrint();
      format.setNewlines(newlines);
      format.setTrimText(trim);
      format.setXHTML(isXHTML);
      format.setExpandEmptyElements(expandEmpty);

      HTMLWriter writer = new HTMLWriter(sw, format);
      Document document = DocumentHelper.parseText(html);
      writer.write(document);
      writer.flush();

      return sw.toString();
   }

   // Allows us to the current state of the format in this struct on the
   // formatStack.
   private class FormatState
   {
      private boolean newlines = false;

      private boolean trimText = false;

      private String indent = "";

      public FormatState(boolean newLines, boolean trimText, String indent)
      {
         this.newlines = newLines;
         this.trimText = trimText;
         this.indent = indent;
      }

      public boolean isNewlines()
      {
         return newlines;
      }

      public boolean isTrimText()
      {
         return trimText;
      }

      public String getIndent()
      {
         return indent;
      }
   }
}

/*
 * <html> <head> <title>My Title </title> <style> .foo { text-align: Right; }
 * </style> <script> function mojo(){ return "bar"; } </script> <script
 * language="JavaScript"> <!-- //this is the canonical javascript hiding.
 * function foo(){ return "foo"; } //--> </script> </head> <!-- this is a
 * comment --> <body bgcolor="#A4BFDD" mojo="&amp;"> entities: &#160; &amp;
 * &quot; &lt; &gt; %23 <p></p> <mojo> </mojo> <foo /> <table border="1"> <tr>
 * <td><pre> line0 <hr /> line1 <b>line2, should line up, indent-wise </b> line
 * 3 line 4 </pre></td><td></td></tr> </table> <myCDATAElement> <![CDATA[My
 * data]]> </myCDATAElement> </body> </html>
 */

/*
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided that the
 * following conditions are met:
 * 
 * 1. Redistributions of source code must retain copyright statements and
 * notices. Redistributions must also contain a copy of this document.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. The name "DOM4J" must not be used to endorse or promote products derived
 * from this Software without prior written permission of MetaStuff, Ltd. For
 * written permission, please contact dom4j-info@metastuff.com.
 * 
 * 4. Products derived from this Software may not be called "DOM4J" nor may
 * "DOM4J" appear in their names without prior written permission of MetaStuff,
 * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
 * 
 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
 * 
 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 */
